package org.lock.spring.interceptor;

import java.util.concurrent.TimeUnit;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.lock.spring.LockManager;
import org.lock.spring.exception.GetLockFailException;
import org.lock.spring.exception.GetLockTimeOutException;
import org.lock.spring.exception.ReleaseLockException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
 * lock interceptor
 * @author chun
 *
 */
public class LockInterceptor implements MethodInterceptor,InitializingBean{
	
	private Logger logger = LoggerFactory.getLogger(LockInterceptor.class);

	private LockAttributeSource lockAttributeSource;
	private LockManager lockManager;
	private LockExceptionHandler lockExceptionHandler;
	private ExpressionParser expressionParser;
	private ParserContext parserContext;
	
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		long startTime = System.currentTimeMillis();
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		//  get annontation lock attribute
		LockAttribute lockAttribute = lockAttributeSource.getLockAttribute(invocation.getMethod(), targetClass);
		String lockName = null;
		String express = null;
		if (lockAttribute == null || LockAttribute.NULL_LOCK_ATTRIBUTE.equals(lockAttribute)){
			try{
				return invocation.proceed();
			}catch(Throwable e){
				if(logger.isInfoEnabled()){
					logger.info("invoke method "+invocation.getMethod()+" exception, without lock ");
				}
				lockExceptionHandler.otherException(e, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
			}
		}
		express = lockAttribute.getName();
		try{
			StandardEvaluationContext context = new StandardEvaluationContext(invocation.getArguments());
			context.setVariable("params", invocation.getArguments());
			express = targetClass.getName()+"/"+invocation.getMethod().getName()+"="+expressionParser.parseExpression(express, parserContext).getValue(context, String.class);
			lockName = lockManager.getLock(express,lockAttribute.getLockTime(),TimeUnit.MILLISECONDS);
			if(logger.isInfoEnabled()){
				logger.info("get lock "+lockName+" success, with path "+express);
			}
			// 反射执行结果
			Object result =  invocation.proceed();
			long endTime = System.currentTimeMillis();
			if(logger.isDebugEnabled()){
				logger.debug("the own the lock time is "+(endTime-startTime));
			}
			return result;
		}catch(GetLockTimeOutException lockTimeOutException){
			if(logger.isInfoEnabled()){
				logger.info("get lock "+express+ " time out for "+lockAttribute.getLockTime());
			}
			lockExceptionHandler.getLockTimeoutException(lockTimeOutException, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
			return null;
		}catch(GetLockFailException getLockFailException){
			if(logger.isInfoEnabled()){
				logger.info("get lock "+express+ " fail. the reason is "+getLockFailException.getMessage(),getLockFailException);
			}
			lockExceptionHandler.getLockException(getLockFailException, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
			return null;
		}catch(Throwable e){
			if(logger.isInfoEnabled()){
				logger.info("invoke method "+invocation.getMethod().getName()+ " fail. the reason is "+e.getMessage(),e);
			}
			lockExceptionHandler.otherException(e, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
			return null;
		}
		finally{
			if(lockName != null){
				try{
					lockManager.releaseLock(lockName);
					if(logger.isInfoEnabled()){
						logger.info("release lock "+lockName+" success");
					}
				}catch(ReleaseLockException releaseLockException){
					if(logger.isInfoEnabled()){
						logger.info("release lock "+express+ "fail. the reason is "+releaseLockException.getMessage(),releaseLockException);
					}
					lockExceptionHandler.releaseLockException(releaseLockException, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
				}catch(Throwable e){
					if(logger.isInfoEnabled()){
						logger.info("release lock "+express+ "fail. the reason is "+e.getMessage(),e);
					}
					lockExceptionHandler.otherException(e, lockName, express, invocation.getMethod().getName(), invocation.getArguments());
				}
			}
		}
	}
	
	/**
	 * @return the {@link #lockAttributeSource}
	 */
	public LockAttributeSource getLockAttributeSource() {
		return lockAttributeSource;
	}

	/**
	 * @param lockAttributeSource
	 * the {@link #lockAttributeSource} to set
	 */
	public void setLockAttributeSource(LockAttributeSource lockAttributeSource) {
		this.lockAttributeSource = lockAttributeSource;
	}

	/**
	 * @return the {@link #lockManager}
	 */
	public LockManager getLockManager() {
		return lockManager;
	}

	/**
	 * @param lockManager
	 * the {@link #lockManager} to set
	 */
	public void setLockManager(LockManager lockManager) {
		this.lockManager = lockManager;
	}

	public LockExceptionHandler getLockExceptionHandler() {
		return lockExceptionHandler;
	}

	public void setLockExceptionHandler(LockExceptionHandler lockExceptionHandler) {
		this.lockExceptionHandler = lockExceptionHandler;
	}

	public ExpressionParser getExpressionParser() {
		return expressionParser;
	}

	public void setExpressionParser(ExpressionParser expressionParser) {
		this.expressionParser = expressionParser;
	}

	public ParserContext getParserContext() {
		return parserContext;
	}

	public void setParserContext(ParserContext parserContext) {
		this.parserContext = parserContext;
	}

	/**
	 * check lockManager、lockAttributeSource
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		if (this.lockManager == null) {
			throw new IllegalStateException(
					"Setting the property 'transactionManager' is required");
		}
		if (this.lockAttributeSource == null) {
			throw new IllegalStateException(
					"Settin the property 'lockAttributeSource' is required" );
		}
		if (this.expressionParser == null) {
			throw new IllegalStateException(
					"Settin the property 'expressionParser' is required" );
		}
		if (this.parserContext == null) {
			throw new IllegalStateException(
					"Settin the property 'parserContext' is required" );
		}
		if (this.lockExceptionHandler == null){
			this.lockExceptionHandler = new DefaultLockExceptionHandler();
		}
	}
	
}
